using System;
using Microsoft.VisualStudio.Debugger.Interop;
using Red.Scripts.Debugger.Defines;

namespace Red.Scripts.Debugger.Remote.Debugger
{
    // This class implements IDebugThread2 which represents a thread running in a program.
    public class Thread : IDebugThread2, IDebugThread100
    {
        #region Public Properties

        public Program Parent { get; private set; }
        public comm.ScriptThread Data { get; set; }

        #endregion Public Properties

        #region C-Tor

        public Thread( Program program )
        {
            Parent = program;
        }

        #endregion C-Tor

        #region IDebugThread2

        /// <summary>
        /// Determines whether the next statement can be set to the given stack frame and code context.
        /// </summary>
        int IDebugThread2.CanSetNextStatement( IDebugStackFrame2 stackFrame, IDebugCodeContext2 codeContext )
        {
            return Constants.S_FALSE;
        }

        // Retrieves a list of the stack frames for this thread.
        int IDebugThread2.EnumFrameInfo( enum_FRAMEINFO_FLAGS dwFieldSpec, uint nRadix, out IEnumDebugFrameInfo2 enumObject )
        {
            System.Diagnostics.Debug.Assert( Data != null, "Message must be set before Thread is used by VS API" );

            FRAMEINFO[] frames = new FRAMEINFO[ Data.frames.Count ];

            for( int i = 0; i < Data.frames.Count; ++i )
            {
                StackFrame stackframe = new StackFrame( this, Data.frames[ i ] );
                frames[ i ] = stackframe.CreateFrameInfo( dwFieldSpec );
            }

            enumObject = new Defines.Enumerators.FrameInfo( frames );

            return Constants.S_OK;
        }

        // Get the name of the thread. For the sample engine, the name of the thread is always "Sample Engine Thread"
        int IDebugThread2.GetName( out string threadName )
        {
            threadName = "Red Scripts";

            return Constants.S_OK;
        }

        // Return the program that this thread belongs to.
        int IDebugThread2.GetProgram( out IDebugProgram2 program )
        {
            program = Parent;

            return Constants.S_OK;
        }

        // Gets the system thread identifier.
        int IDebugThread2.GetThreadId( out uint threadId )
        {
            threadId = Data.id;

            return Constants.S_OK;
        }

        // Gets properties that describe a thread.
        int IDebugThread2.GetThreadProperties( enum_THREADPROPERTY_FIELDS dwFields, THREADPROPERTIES[] propertiesArray )
        {
            if( Utility.HasFlag( dwFields, enum_THREADPROPERTY_FIELDS.TPF_ID ) )
            {
                propertiesArray[ 0 ].dwThreadId = Data.id;
                propertiesArray[ 0 ].dwFields = enum_THREADPROPERTY_FIELDS.TPF_ID;
            }

            if ( Utility.HasFlag( dwFields, enum_THREADPROPERTY_FIELDS.TPF_SUSPENDCOUNT ) )
            {
                propertiesArray[ 0 ].dwFields |= enum_THREADPROPERTY_FIELDS.TPF_SUSPENDCOUNT;
            }

            if ( ( dwFields & enum_THREADPROPERTY_FIELDS.TPF_STATE ) != 0 )
            {
                propertiesArray[ 0 ].dwThreadState = (uint)enum_THREADSTATE.THREADSTATE_RUNNING;
                propertiesArray[ 0 ].dwFields |= enum_THREADPROPERTY_FIELDS.TPF_STATE;
            }

            if ( ( dwFields & enum_THREADPROPERTY_FIELDS.TPF_PRIORITY ) != 0 )
            {
                propertiesArray[ 0 ].bstrPriority = "Normal";
                propertiesArray[ 0 ].dwFields |= enum_THREADPROPERTY_FIELDS.TPF_PRIORITY;
            }

            if ( ( dwFields & enum_THREADPROPERTY_FIELDS.TPF_NAME ) != 0 )
            {
                propertiesArray[ 0 ].bstrName = "Red Scripts (Props)";
                propertiesArray[ 0 ].dwFields |= enum_THREADPROPERTY_FIELDS.TPF_NAME;
            }

            if ( ( dwFields & enum_THREADPROPERTY_FIELDS.TPF_LOCATION ) != 0 )
            {
                propertiesArray[ 0 ].bstrLocation = Data.frames[ 0 ].func;
                propertiesArray[ 0 ].dwFields |= enum_THREADPROPERTY_FIELDS.TPF_LOCATION;
            }

            return Constants.S_OK;
        }

        // Resume a thread.
        // This is called when the user chooses "Unfreeze" from the threads window when a thread has previously been frozen.
        int IDebugThread2.Resume( out uint suspendCount )
        {
            suspendCount = 0;
            return Constants.E_NOTIMPL;
        }

        // Sets the next statement to the given stack frame and code context.
        // The sample debug engine doesn't support set next statement
        int IDebugThread2.SetNextStatement( IDebugStackFrame2 stackFrame, IDebugCodeContext2 codeContext )
        {
            return Constants.E_NOTIMPL;
        }

        // suspend a thread.
        // This is called when the user chooses "Freeze" from the threads window
        int IDebugThread2.Suspend( out uint suspendCount )
        {
            suspendCount = 0;
            return Constants.E_NOTIMPL;
        }

        #endregion IDebugThread2

        #region IDebugThread100 Members

        int IDebugThread100.SetThreadDisplayName( string name )
        {
            // Not necessary to implement in the debug engine. Instead
            // it is implemented in the SDM.
            return Constants.E_NOTIMPL;
        }

        int IDebugThread100.GetThreadDisplayName( out string name )
        {
            // Not necessary to implement in the debug engine. Instead
            // it is implemented in the SDM, which calls GetThreadProperties100()
            name = "";
            return Constants.E_NOTIMPL;
        }

        // Returns whether this thread can be used to do function/property evaluation.
        int IDebugThread100.CanDoFuncEval()
        {
            return Constants.S_FALSE;
        }

        int IDebugThread100.SetFlags( uint flags )
        {
            // Not necessary to implement in the debug engine. Instead
            // it is implemented in the SDM.
            return Constants.E_NOTIMPL;
        }

        int IDebugThread100.GetFlags( out uint flags )
        {
            // Not necessary to implement in the debug engine. Instead
            // it is implemented in the SDM.
            flags = 0;
            return Constants.E_NOTIMPL;
        }

        int IDebugThread100.GetThreadProperties100( uint dwFields, THREADPROPERTIES100[] props )
        {
            // Invoke GetThreadProperties to get the VS7/8/9 properties
            THREADPROPERTIES[] props90 = new THREADPROPERTIES[ 1 ];
            enum_THREADPROPERTY_FIELDS dwFields90 = (enum_THREADPROPERTY_FIELDS)( dwFields & 0x3f );
            int hRes = ( (IDebugThread2)this ).GetThreadProperties( dwFields90, props90 );
            props[ 0 ].bstrLocation = props90[ 0 ].bstrLocation;
            props[ 0 ].bstrName = props90[ 0 ].bstrName;
            props[ 0 ].bstrPriority = props90[ 0 ].bstrPriority;
            props[ 0 ].dwFields = (uint)props90[ 0 ].dwFields;
            props[ 0 ].dwSuspendCount = props90[ 0 ].dwSuspendCount;
            props[ 0 ].dwThreadId = props90[ 0 ].dwThreadId;
            props[ 0 ].dwThreadState = props90[ 0 ].dwThreadState;

            // Populate the new fields
            if ( hRes == Constants.S_OK && dwFields != (uint)dwFields90 )
            {
                if ( Utility.HasFlag( dwFields, enum_THREADPROPERTY_FIELDS100.TPF100_DISPLAY_NAME ) )
                {
                    // Thread display name is being requested
                    props[ 0 ].bstrDisplayName = "Red Scripts Display Name";
                    props[ 0 ].dwFields |= (uint)enum_THREADPROPERTY_FIELDS100.TPF100_DISPLAY_NAME;

                    // Give this display name a higher priority than the default (0)
                    // so that it will actually be displayed
                    props[ 0 ].DisplayNamePriority = 10;
                    props[ 0 ].dwFields |= (uint)enum_THREADPROPERTY_FIELDS100.TPF100_DISPLAY_NAME_PRIORITY;
                }

                if ( Utility.HasFlag( dwFields, enum_THREADPROPERTY_FIELDS100.TPF100_CATEGORY ) )
                {
                    props[ 0 ].dwThreadCategory = 0;
                    props[ 0 ].dwFields |= (uint)enum_THREADPROPERTY_FIELDS100.TPF100_CATEGORY;
                }

                if ( Utility.HasFlag( dwFields, enum_THREADPROPERTY_FIELDS100.TPF100_ID ) )
                {
                    props[ 0 ].dwFields |= (uint)enum_THREADPROPERTY_FIELDS100.TPF100_ID;
                }

                if ( Utility.HasFlag( dwFields, enum_THREADPROPERTY_FIELDS100.TPF100_AFFINITY ) )
                {
                    // Thread cpu affinity is being requested
                    props[ 0 ].AffinityMask = 0;
                    props[ 0 ].dwFields |= (uint)enum_THREADPROPERTY_FIELDS100.TPF100_AFFINITY;
                }

                if ( Utility.HasFlag( dwFields, enum_THREADPROPERTY_FIELDS100.TPF100_PRIORITY_ID ) )
                {
                    // Thread display name is being requested
                    props[ 0 ].priorityId = 0;
                    props[ 0 ].dwFields |= (uint)enum_THREADPROPERTY_FIELDS100.TPF100_PRIORITY_ID;
                }

            }

            return hRes;
        }

        #endregion

        #region Uncalled interface methods
        // These methods are not currently called by the Visual Studio debugger, so they don't need to be implemented

        int IDebugThread2.GetLogicalThread( IDebugStackFrame2 stackFrame, out IDebugLogicalThread2 logicalThread )
        {
            throw new NotImplementedException();
        }

        int IDebugThread2.SetThreadName( string name )
        {
            throw new NotImplementedException();
        }

        #endregion
    }
}
